/* 
 * $Id: ActionRepository.java 1259 2007-01-09 14:23:47Z tcoupaye $
 *
 * Behavior Protocols extensions for static and runtime checking
 * developed for the Julia implementation of Fractal.
 *
 * Copyright 2004
 *    Distributed Systems Research Group
 *    Department of Software Engineering
 *    Faculty of Mathematics and Physics
 *    Charles University, Prague
 *
 * Copyright (C) 2006
 *    Formal Methods In Software Engineering Group
 *    Institute of Computer Science
 *    Academy of Sciences of the Czech Republic
 *
 * Copyright (C) 2006 France Telecom
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 * Contact: ft@nenya.ms.mff.cuni.cz
 * Authors: Jan Kofron <kofron@nenya.ms.mff.cuni.cz>
 */


package org.objectweb.fractal.bpc.checker.node;

import java.util.LinkedList;
import java.util.TreeMap;
import java.util.NoSuchElementException;
import java.util.TreeSet;
import java.util.Iterator;

import org.objectweb.fractal.bpc.checker.parser.*;


/**
 * This class stores the string action identifier and maps integral numbers to
 * it for faster access. For each action there are stored 10 variants - 
 * a, !a, ?a, !a^, ?a^, !a$, ?a$, #a, #a^, #a$
 * 0,  1,  2,  3,   4,   5,   6,   7,  8,   9
 */
public class ActionRepository {

    /** Creates a new instance of ActionRepository */
    public ActionRepository() {
        repository = new TreeMap();
        inverseRepository = new TreeMap();
        atomicRepository = new TreeMap();
        atomicInverseRepository = new TreeMap();
        usedItems = new TreeSet();
    }

    /**
     * Adds the item to the database.
     * 
     * @return the index of this new item
     */
    public int addItem(String item) throws SyntaxErrorException {
        Integer i;

        Debug.println(3, "Adding item to repository: " + item);

        try {
            i = (Integer) inverseRepository.get(item);
        } catch (ClassCastException e) {
            i = null;
        } catch (NullPointerException e) {
            i = null;
        }

        if (i != null) { // the item is already present within the database
            touchItem(i.intValue());
            return i.intValue();
        }

        else { // the item is NOT present within the database
            int index;
            try {
                index = ((Integer) repository.lastKey()).intValue() + 1;
            } catch (NoSuchElementException e) {
                index = 0;
            }

            String next;

            String purename = getPureName(item);
            //Debug.println("ActionRepository: purename: " + purename);
            repository.put(new Integer(index), purename);

            inverseRepository.put(purename, new Integer(index));

            next = "!" + purename;
            ++index;
            repository.put(new Integer(index), next);
            inverseRepository.put(next, new Integer(index));

            next = "?" + purename;
            ++index;
            repository.put(new Integer(index), next);
            inverseRepository.put(next, new Integer(index));

            next = "!" + purename + "^";
            ++index;
            repository.put(new Integer(index), next);
            inverseRepository.put(next, new Integer(index));

            next = "?" + purename + "^";
            ++index;
            repository.put(new Integer(index), next);
            inverseRepository.put(next, new Integer(index));

            next = "!" + purename + "$";
            ++index;
            repository.put(new Integer(index), next);
            inverseRepository.put(next, new Integer(index));

            next = "?" + purename + "$";
            ++index;
            repository.put(new Integer(index), next);
            inverseRepository.put(next, new Integer(index));

            next = "#" + purename;
            ++index;
            repository.put(new Integer(index), next);
            inverseRepository.put(next, new Integer(index));

            next = "#" + purename + "^";
            ++index;
            repository.put(new Integer(index), next);
            inverseRepository.put(next, new Integer(index));

            next = "#" + purename + "$";
            ++index;
            repository.put(new Integer(index), next);
            inverseRepository.put(next, new Integer(index));

            Debug.println(3, "Action added");

            Integer returnindex = (Integer) inverseRepository.get(item);
            if (returnindex == null) {
                throw new SyntaxErrorException("This is not an event: " + item);
            }
            touchItem(returnindex.intValue());
            return (returnindex).intValue();

        }
    }
    
    /**
     * Adds the atomic action (and all variants) to the repository. 
     * 
     * @param events the events forming this atomic action
     * @return the id of the atomic action
     */
    public int addAtomicItem(TreeSet events) {
        Integer i;
        
        try {
            i = (Integer) atomicInverseRepository.get(events);
        } catch (ClassCastException e) {
            i = null;
        } catch (NullPointerException e) {
            i = null;
        }

        if (i != null) { // the item is already present within the database
            touchItem(i.intValue());
            return i.intValue();
        }
        else {
	       
	        LinkedList copy = new LinkedList();
	        copy.addAll(events);
	        LinkedList variants = this.createAtomicVariants(copy);
	        
	        Iterator it = variants.iterator();
	        while (it.hasNext()) {
                Integer index = new Integer(-1 - atomicRepository.size());
	            
	            AtomicAction item = new AtomicAction((LinkedList)it.next());
	            atomicRepository.put(index,item);
	            atomicInverseRepository.put(item, index);
                
                // add the inverse action
                AtomicAction inverseaction = new AtomicAction();
                for (Iterator it2 = item.iterator(); it2.hasNext(); )
                    inverseaction.add(new Integer(this.getInverseAction(((Integer)it2.next()).intValue())));
                
                index = new Integer(-1 - atomicRepository.size());
                
                atomicRepository.put(index,inverseaction);
                atomicInverseRepository.put(inverseaction, index);

	        }
            
	        return ((Integer)atomicInverseRepository.get(new AtomicAction(events))).intValue();
        }
    }

    /**
     * @return all used indices of actions.
     */
    public TreeSet getAllUsedEventIndices() {
        return new TreeSet(usedItems);

        /*
         * TreeSet items = new TreeSet();
         * 
         * for (int i = 0; i < repository.size(); ++i) items.add(new
         * Integer(i));
         * 
         * return items; /
         */
    }

    /**
     * Tests the presence of a key in the database.
     * 
     * @return whether the action identifier with specified index is present in
     *         the repository
     */
    public boolean getItemPresence(int index) {
        return repository.containsKey(new Integer(index));
    }

    /**
     * @return the textual form of the item If the key is not present, null is
     *         returned (note that this is the default behavior, not the
     *         consequence of an exception occurence).
     */
    public String getItemString(int index) {
        try {
            if (index >= 0)
                return (String) repository.get(new Integer(index));
            else {
                String result = new String("[");
                TreeSet actions = (TreeSet)atomicRepository.get(new Integer(index));
                Iterator it = actions.iterator();
                while (it.hasNext()) {
                    result = result + getItemString(((Integer)it.next()).intValue());
                    if (it.hasNext())
                        result += ", ";
                }
                result += "]";
                
                return result;
            }
        } catch (NullPointerException e) {
            return null;
        }
    }

    /**
     * @return the index of the given action
     */
    public int getItemIndex(String item) {
        try {
            Integer result = (Integer) inverseRepository.get(item);
            touchItem(result.intValue());
            return result.intValue();
        } catch (NullPointerException e) {
            if (!item.equals("")) 
                Debug.println(1, "Action repository: try to access action '" + item + "' - not present in repository.\n");
            throw e;
        }
    }
    
    /**
     * Gets the index of an atomic action.
     * 
     * @param action atomic action
     * @return the index of the action
     */
    public int getItemIndex(AtomicAction action) {
        try {
            Integer result = (Integer)atomicInverseRepository.get(action);
            return result.intValue();
        } catch (NullPointerException e) {
            if (!action.equals(null))
                Debug.println(1, "Action repository: try to access atomic action '" + action + "'  - not present in repository.\n");
            throw e;
        }
           
    }

    /**
     * The method returns the index of the basic variant of the operation
     * specified. It doesn't check whether the event index is valid.
     * 
     * @return the pure index of the name of the action
     */
    static public int getPure(int event) {
        return (event / VARIANTCNT) * VARIANTCNT;
    }

    /**
     * It doesn't check whether the event index is valid. If there's no inverse
     * action, it returns back the original index.
     * 
     * @return the inverse action
     */
    public int getInverseAction(int event) {
        if (event < 0)
            return invertAtomic(event);
        if (((event % VARIANTCNT) < 7) && (event % VARIANTCNT > 0)) {
            if ((event % 2) == 1) {
                touchItem(event + 1);
                return event + 1;
            } else {
                touchItem(event - 1);
                return event - 1;
            }
        }

        else
            return event;
    }
    
    /**
     * Invert the atomic action
     * @param eventindex index of the action to be inverted
     * @return index of inverted atomic action
     */
    public int invertAtomic(int eventindex) {
        AtomicAction aa = new AtomicAction();
        for (Iterator it = this.getAtomicEvents(eventindex).iterator(); it.hasNext(); ) {
            aa.add(new Integer(this.getInverseAction(((Integer)it.next()).intValue())));
        }
        
        return this.getItemIndex(aa);
    }
    
    /**
     * @return the index of corresponing tau action
     */
    public int getTauAction(int event) {
        int root = (event / VARIANTCNT) * VARIANTCNT;
        int index = event % VARIANTCNT;

        if ((index == 3) || (index == 4)) {
            touchItem(root + 8);
            return root + 8;
        } else if ((index == 5) || (index == 6)) {
            touchItem(root + 9);
            return root + 9;
        } else {
            touchItem(root + 7);
            return root + 7;
        }
    }

    /**
     * @param event
     *            the event index
     * @return true iff this is a response action
     */
    public boolean isResponse(int event) {
        int subtype = event % VARIANTCNT;
        return (subtype == 2) || (subtype == 4) || (subtype == 6);
    }

    /**
     * @param event
     *            the event index
     * @return true iff this is a tau action
     */
    public boolean isTau(int event) {
        int subtype = event % VARIANTCNT;
        return subtype >= 7;
    }

    /**
     * 
     * @param event
     *            event index
     * @return the event index of the action corresponding to the event (the
     *         code corresponding to the name without the last character
     *         (request/response mark))
     */
    public int getEventAction(int event) {
        if ((event % VARIANTCNT == 3) || (event % VARIANTCNT == 5))
            return event / 10 * 10 + 1;
        else if ((event % VARIANTCNT == 4) || (event % VARIANTCNT == 6))
            return event / 10 * 10 + 2;
        else if ((event % VARIANTCNT == 8) || (event % VARIANTCNT == 9))
            return event / 10 * 10 + 7;
        else
            return event;
    }

    /**
     * Parses the name and return the pure name of the action.
     * 
     * @return pure name of the action
     */
    static private String getPureName(String action) {
        String result = new String(action);

        char first = action.charAt(0);
        char last = action.charAt(action.length() - 1);

        if ((first == '?') || (first == '!') || (first == '#'))
            result = result.substring(1);

        if ((last == '^') || (last == '$'))
            result = result.substring(0, result.length() - 1);

        return result;
    }

    /**
     * Touches the item in the repository. Doesn't touch those items that don't
     * represent real actions.
     */
    private void touchItem(int index) {
        if ((index % VARIANTCNT != 0) && (index % VARIANTCNT != 1) && (index % VARIANTCNT != 2) && (index % VARIANTCNT != 7))
            usedItems.add(new Integer(index));
    }
    
    /**
     * @param atomicactionindex
     * @return the AtomicAction at the given index
     */
    public AtomicAction getAtomicEvents(int atomicactionindex) {
        return (AtomicAction)atomicRepository.get(new Integer(atomicactionindex));
    }

    
    
    /**
     * Generates all variants of an atomic events.
     */
    private LinkedList createAtomicVariants(LinkedList events) {
        LinkedList result = new LinkedList();
        LinkedList subresult1;
        LinkedList subresult2 = new LinkedList();
        
        if (events.size() == 0)
            return result; 
        
        if (events.size() == 1) {
            subresult2.add(events.getFirst());
            result.add(subresult2);
            subresult2 = new LinkedList();
            subresult2.add(new Integer(getTauAction(((Integer)events.getFirst()).intValue())));
            result.add(subresult2);
            return result;
        }
        
        Integer first = (Integer)events.removeFirst();
        
        subresult1 = createAtomicVariants(events);
        subresult2 = duplicateList(subresult1);

        
        Iterator it = subresult1.iterator();
        while (it.hasNext()) {
            LinkedList item = (LinkedList)it.next();
            item.add(first);
            result.add(item);
        }
        
        Integer tau = new Integer(getTauAction(first.intValue()));
        it = subresult2.iterator();
        while (it.hasNext()) {
            LinkedList item = (LinkedList)it.next();
            item.add(tau);
            result.add(item);
        }
        
        return result;
    }
    
    /**
     * Duplicates the list of lists.
     * @return the duplicated list
     */
    private LinkedList duplicateList(LinkedList source) {
        LinkedList newlist = new LinkedList();
        
        Iterator it = source.iterator();
        
        while(it.hasNext()) {
            LinkedList item = (LinkedList)it.next();
            LinkedList newitem = new LinkedList();
            
            Iterator internal = item.iterator();
                        
            while (internal.hasNext()) {
                newitem.add(new Integer(((Integer)internal.next()).intValue()));
            }
            
            newlist.add(newitem);
        }
        
        return newlist;
    }

    /**
     * Prints out the content of the cache.
     */
    public void flushCache() {
        Iterator it = repository.keySet().iterator();

        while (it.hasNext()) {
            System.out.println(repository.get((Integer) it.next()));
        }
    }

    //--------------------------------------------------------------------------

    /**
     * The map where the identifiers of action are stored
     */
    private TreeMap repository;

    /**
     * Inverse database for presence checking. It maps the string indentifiers
     * to their indices
     */
    private TreeMap inverseRepository;

    /**
     * The map where the identifiers of action are stored
     */
    private TreeMap atomicRepository;

    /**
     * Inverse database for presence checking. It maps the string indentifiers
     * to their indices
     */
    private TreeMap atomicInverseRepository;

    /**
     * The number of variants per event
     */
    static final int VARIANTCNT = 10;

    /**
     * Action used within the input protocols
     */
    private TreeSet usedItems;

}
